﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace Pfz.Threading.Cooperative
{
	/// <summary>
	/// Class that simulates a stack-saving mechanism (which does not exist in .Net).
	/// If in the future such mechanism exists, this class may be killed or simple redirect to it.
	/// At this moment, the "saved stack" runs in another thread completely synchronized with
	/// the main one. But this can cause dead-locks if the caller holds a lock that the calle
	/// should also use.
	/// Aside from that, you can YieldReturn at any moment, even inside sub-methods that are
	/// not enumerable.
	/// </summary>
	public sealed class UnsafeStackSaver:
		IAdvancedDisposable,
		IEnumerable<bool>,
		IEnumerator<bool>
	{
		#if !SILVERLIGHT
		internal static readonly object _syncAbortToken = new object();
		#endif

		#if DEBUG
			private Thread _thisThread;
		#endif

		private readonly object _executePulser = new object();
		private Action _action;
		private bool _wasDisposed;
		private bool _executing;
		
		/// <summary>
		/// Creates a new StackSaver that will run the given action, allowing it to
		/// YieldReturn at any moment. It will not generate values, so it is used as
		/// a synchronization mechanism.
		/// </summary>
		public UnsafeStackSaver(Action action)
		{
			_action = action;

			UnlimitedThreadPool.Run(_Start);
		}
		
		/// <summary>
		/// Releases the stack-saver. It will kill the executing method the same way a thread is aborted
		/// (with a ThreadAbortException, calling only catches and finallys).
		/// </summary>
		public void Dispose()
		{
			lock(_executePulser)
			{
				_wasDisposed = true;
				Monitor.Pulse(_executePulser);
				
				while(_action != null)
					Monitor.Wait(_executePulser);
			}
		}
		
		/// <summary>
		/// Gets a value indicating if a Dispose call was already made on this object,
		/// even if it was not finalized yet.
		/// </summary>
		public bool WasDisposed
		{
			get
			{
				return _wasDisposed;
			}
		}
		
		/// <summary>
		/// Gets the exception that finished this stack-saver.
		/// </summary>
		public Exception Exception { get; private set; }

		[ThreadStatic]
		private static UnsafeStackSaver _current;
		/// <summary>
		/// When inside the action of a stack-saver, the stack-saver can be discovered by this.
		/// If the return is null, then this method was called directly or by a value-producing stack-saver.
		/// </summary>
		public static UnsafeStackSaver Current
		{
			get
			{
				return _current;
			}
		}
		
		[ThreadStatic]
		internal static IValueProducerStackSaver _currentValueProducer;
		/// <summary>
		/// When inside the action of a stack-saver that produces values, the stack-saver can 
		/// be discovered by this. It will use a base interface, so you don't need to know its exact type
		/// (it will work as long as you produce castable values).
		/// If the return is null, then this method was called directly or by a non-value producing stack-saver.
		/// </summary>
		public static IValueProducerStackSaver CurrentValueProducer
		{
			get
			{
				return _currentValueProducer;
			}
		}

		private void _Start()
		{
			try
			{
				var thisThread = Thread.CurrentThread;
				thisThread.IsBackground = true;

				#if DEBUG
					_thisThread = thisThread;
				#endif

				_current = this;
		
				lock(_executePulser)
					_Wait();
					
				_action();
			}
			#if !SILVERLIGHT
			catch(ThreadAbortException abortException)
			{
				// if it is not our abort, rethrow.
				if (abortException.ExceptionState != _syncAbortToken)
				{
					Exception = abortException;
					throw;
				}

				// if it is our abort, it was used only to arrive here, so
				// now we can resetabort and let the thread return to the pool.
				Thread.ResetAbort();
			}
			#endif
			catch(Exception exception)
			{
				Exception = exception;
			}
			finally
			{
				#if DEBUG
					_thisThread = null;
				#endif

				_current = null;
				_action = null;
				_executing = false;
				
				lock(_executePulser)
					Monitor.PulseAll(_executePulser);
			}
		}

		/// <summary>
		/// Yield returns to a stack-saver that does not produce values, considering there is one.
		/// If a stack-saver does not exist, this method throws an InvalidOperationException.
		/// </summary>
		public static void StaticYieldReturn()
		{
			var stackSaver = _current;
			if (stackSaver == null)
				throw new InvalidOperationException("This method is not managed by a non-returning stack-saver.");
				
			stackSaver.YieldReturn();
		}

		/// <summary>
		/// Yield returns to a typed stack-saver, considering there is one.
		/// If a stack-saver does not exist, an InvalidOperationException is thrown.
		/// Note that to return a string as an object, you should not allow
		/// the type-inference.
		/// </summary>
		public static void StaticYieldReturn<T>(T value)
		{
			var instance = UnsafeStackSaver<T>._current;
			if (instance != null)
			{
				instance.YieldReturn(value);
				return;
			}
			
			var interfaced = _currentValueProducer;
			if (interfaced == null)
				throw new InvalidOperationException("This method is not managed by any returning stack-saver.");
				
			interfaced.YieldReturn(value);
		}
		
		/// <summary>
		/// YieldReturns to the caller of this stack-saver.
		/// </summary>
		public void YieldReturn()
		{
			#if DEBUG
				if (Thread.CurrentThread != _thisThread)
					throw new InvalidOperationException("Yield return may only be called by the action thread when it executes.");
			#endif
				
			lock(_executePulser)
			{
				_executing = false;
				Monitor.Pulse(_executePulser);

				_Wait();
			}
		}

		private void _Wait()
		{
			while(true)
			{
				if (_wasDisposed)
				{
					#if SILVERLIGHT
						Thread.CurrentThread.Abort();
					#else
						Thread.CurrentThread.Abort(_syncAbortToken);
					#endif
					// if the stacksaver was disposed, we must kill
					// the actual thread. Calling abort on the actual thread
					// does not cause an asynchronous exception, so it is safe.
				}
				
				if (_executing)
					return;
					
				Monitor.Wait(_executePulser);
			}
		}

		/// <summary>
		/// Executes the method of this stack-saver until it ends or until it calls
		/// a YieldReturn.
		/// Returns true if it YieldReturned, false if the action ended.
		/// If an exception is thrown on the called method, it is rethrown as a 
		/// TargetInvocationException.
		/// </summary>
		public bool Execute()
		{
			BeginExecute();
			return EndExecute();
		}

		/// <summary>
		/// Executes the method of this stack-saver until it ends or until it calls
		/// a YieldReturn.
		/// Returns true if it YieldReturned, false if the action ended.
		/// If an exception is thrown, it is stored in the Exception property.
		/// </summary>
		public bool ExecuteNoException()
		{
			BeginExecute();
			return EndExecuteNoException();
		}

		/// <summary>
		/// Starts the execution of the method of this StackSaver in parallel.
		/// </summary>
		public void BeginExecute()
		{
			lock(_executePulser)
			{
				if (_executing)
					throw new InvalidOperationException("This object is already executing.");
					
				if (_action == null)
					throw new InvalidOperationException("The stacksaver already finished its execution.");
					
				if (_wasDisposed)
					throw new ObjectDisposedException(GetType().FullName);
					
				_executing = true;
				Monitor.Pulse(_executePulser);
			}
		}

		/// <summary>
		/// Waits until the action executed by the BeginExecute finishes.
		/// If the action throws an exception, it is rethrown in this thread as a TargetInvocationException.
		/// </summary>
		/// <returns>true if the action yield returned, false if it ended.</returns>
		public bool EndExecute()
		{
			bool result = EndExecuteNoException();

			var exception = Exception;
			if (exception != null)
				throw new TargetInvocationException(exception);

			return result;
		}

		/// <summary>
		/// Waits until the action executed by the BeginExecute finishes.
		/// If an exception is thrown on the other thread, it is rethrown here as a TargetInvocationException.
		/// </summary>
		/// <returns>true if the action yield returned, false if it ended.</returns>
		public bool EndExecuteNoException()
		{
			lock(_executePulser)
			{
				while(_executing)
					Monitor.Wait(_executePulser);
					
				return _action != null;
			}
		}

		#region IEnumerable<bool> Membres
			IEnumerator<bool> IEnumerable<bool>.GetEnumerator()
			{
				return this;
			}
		#endregion
		#region IEnumerable Membres
			IEnumerator IEnumerable.GetEnumerator()
			{
				return this;
			}
		#endregion

		#region IEnumerator<bool> Membres
			bool IEnumerator<bool>.Current
			{
				get
				{
					return true;
				}
			}
		#endregion
		#region IEnumerator Membres
			internal static readonly object BoxedTrue = true;
			object IEnumerator.Current
			{
				get
				{
					return BoxedTrue;
				}
			}
			bool IEnumerator.MoveNext()
			{
				return Execute();
			}
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		#endregion
	}

	/// <summary>
	/// Class that simulates a stack-saving mechanism (which does not exist in .Net).
	/// If in the future such mechanism exists, this class may be killed or simple redirect to it.
	/// At this moment, the "saved stack" runs in another thread completely synchronized with
	/// the main one. But this can cause dead-locks if the caller holds a lock that the calle
	/// should also use.
	/// Aside from that, you can YieldReturn at any moment, even inside sub-methods that are
	/// not enumerable.
	/// </summary>
	public sealed class UnsafeStackSaver<T>:
		IAdvancedDisposable,
		IEnumerable<T>,
		IEnumerator<T>,
		IValueProducerStackSaver
	{
		#if DEBUG
			private Thread _thisThread;
		#endif

		private readonly object _executePulser = new object();
		private Action _action;
		private bool _executing;
		private bool _wasDisposed;
		
		/// <summary>
		/// Creates a new StackSaver that will run the given action, allowing it to
		/// YieldReturn at any moment. It will not generate values, so it is used as
		/// a synchronization mechanism.
		/// </summary>
		public UnsafeStackSaver(Action action)
		{
			_action = action;

			UnlimitedThreadPool.Run(_Start);
		}
		
		/// <summary>
		/// Releases the stack-saver. It will kill the executing method the same way a thread is aborted
		/// (with a ThreadAbortException, calling only catches and finallys).
		/// </summary>
		public void Dispose()
		{
			lock(_executePulser)
			{
				_wasDisposed = true;
				Monitor.Pulse(_executePulser);
				
				while(_action != null)
					Monitor.Wait(_executePulser);
			}
		}
		
		/// <summary>
		/// Gets a value indicating if a Dispose call was already made on this object,
		/// even if it was not finalized yet.
		/// </summary>
		public bool WasDisposed
		{
			get
			{
				return _wasDisposed;
			}
		}
		
		/// <summary>
		/// Gets the value generated by the last call to Execute.
		/// </summary>
		public T CurrentValue { get; private set; }

		/// <summary>
		/// Gets the exception that finished this stack-saver.
		/// </summary>
		public Exception Exception { get; private set; }

		[ThreadStatic]
		internal static UnsafeStackSaver<T> _current;
		/// <summary>
		/// When inside the action of a stack-saver, the stack-saver can be discovered by this.
		/// If the return is null, then this method was called directly or by a value-producing stack-saver.
		/// </summary>
		public static UnsafeStackSaver<T> Current
		{
			get
			{
				return _current;
			}
		}
		
		private void _Start()
		{
			try
			{
				var thisThread = Thread.CurrentThread;
				thisThread.IsBackground = true;

				#if DEBUG
					_thisThread = thisThread;
				#endif

				_current = this;
				UnsafeStackSaver._currentValueProducer = this;
		
				lock(_executePulser)
					_Wait();
					
				_action();
			}
			#if !SILVERLIGHT
			catch(ThreadAbortException abortException)
			{
				// if it is not our abort, rethrow.
				if (abortException.ExceptionState != UnsafeStackSaver._syncAbortToken)
				{
					Exception = abortException;
					throw;
				}

				// if it is our abort, it was used only to arrive here, so
				// now we can resetabort and let the thread return to the pool.
				Thread.ResetAbort();
			}
			#endif
			catch(Exception exception)
			{
				Exception = exception;
			}
			finally
			{
				#if DEBUG
					_thisThread = null;
				#endif

				_current = null;
				UnsafeStackSaver._currentValueProducer = null;
				_action = null;
				_executing = false;
				
				lock(_executePulser)
					Monitor.PulseAll(_executePulser);
			}
		}

		/// <summary>
		/// YieldReturns to the caller of this stack-saver.
		/// </summary>
		public void YieldReturn(T value)
		{
			#if DEBUG
				if (Thread.CurrentThread != _thisThread)
					throw new InvalidOperationException("Yield return may only be called by the action thread when it executes.");
			#endif
				
			lock(_executePulser)
			{
				_executing = false;
				CurrentValue = value;
				Monitor.Pulse(_executePulser);

				_Wait();
			}
		}

		private void _Wait()
		{
			while(true)
			{
				if (_wasDisposed)
				{
					#if SILVERLIGHT
						Thread.CurrentThread.Abort();
					#else
						Thread.CurrentThread.Abort(UnsafeStackSaver._syncAbortToken);
					#endif
					// if the stacksaver was disposed, we must kill
					// the actual thread. Calling abort on the actual thread
					// does not cause an asynchronous exception, so it is safe.
				}
				
				if (_executing)
					return;
					
				Monitor.Wait(_executePulser);
			}
		}

		/// <summary>
		/// Executes the method of this stack-saver until it ends or until it calls
		/// a YieldReturn.
		/// Returns true if it YieldReturned, false if the action ended.
		/// If an exception is thrown on the called method, it is rethrown as a 
		/// TargetInvocationException.
		/// </summary>
		public bool Execute()
		{
			BeginExecute();
			return EndExecute();
		}

		/// <summary>
		/// Executes the method of this stack-saver until it ends or until it calls
		/// a YieldReturn.
		/// Returns true if it YieldReturned, false if the action ended.
		/// If an exception is thrown, it is stored in the Exception property.
		/// </summary>
		public bool ExecuteNoException()
		{
			BeginExecute();
			return EndExecuteNoException();
		}

		/// <summary>
		/// Starts the execution of the method of this StackSaver in parallel.
		/// </summary>
		public void BeginExecute()
		{
			lock(_executePulser)
			{
				if (_executing)
					throw new InvalidOperationException("This object is already executing.");
					
				if (_action == null)
					throw new InvalidOperationException("The stacksaver already finished its execution.");
					
				if (_wasDisposed)
					throw new ObjectDisposedException(GetType().FullName);
					
				_executing = true;
				Monitor.Pulse(_executePulser);
			}
		}

		/// <summary>
		/// Waits until the action executed by the BeginExecute finishes.
		/// If the action throws an exception, it is rethrown in this thread as a TargetInvocationException.
		/// </summary>
		/// <returns>true if the action yield returned, false if it ended.</returns>
		public bool EndExecute()
		{
			bool result = EndExecuteNoException();

			var exception = Exception;
			if (exception != null)
				throw new TargetInvocationException(exception);

			return result;
		}

		/// <summary>
		/// Waits until the action executed by the BeginExecute finishes.
		/// If an exception is thrown on the other thread, it is rethrown here as a TargetInvocationException.
		/// </summary>
		/// <returns>true if the action yield returned, false if it ended.</returns>
		public bool EndExecuteNoException()
		{
			lock(_executePulser)
			{
				while(_executing)
					Monitor.Wait(_executePulser);
					
				return _action != null;
			}
		}

		#region IEnumerable<T> Membres
			IEnumerator<T> IEnumerable<T>.GetEnumerator()
			{
				return this;
			}
		#endregion
		#region IEnumerable Membres
			IEnumerator IEnumerable.GetEnumerator()
			{
				return this;
			}
		#endregion

		#region IEnumerator<T> Membres
			T IEnumerator<T>.Current
			{
				get
				{
					return CurrentValue;
				}
			}
		#endregion
		#region IEnumerator Membres
			internal static readonly object BoxedTrue = true;
			object IEnumerator.Current
			{
				get
				{
					return BoxedTrue;
				}
			}
			bool IEnumerator.MoveNext()
			{
				return Execute();
			}
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		#endregion
		#region IValueProducerStackSaver Membres
			void IValueProducerStackSaver.YieldReturn(object value)
			{
				YieldReturn((T)value);
			}
		#endregion
	}

}